home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2017 October / PCgo 10-2017 CD-ROM Germany.iso / nw.pak / Unnamed File 000142.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  24.4 KB  |  763 lines

  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. cr.define('cr.ui.pageManager', function() {
  6.   /** @const */ var FocusOutlineManager = cr.ui.FocusOutlineManager;
  7.  
  8.   /**
  9.    * PageManager contains a list of root Page and overlay Page objects and
  10.    * handles "navigation" by showing and hiding these pages and overlays. On
  11.    * initial load, PageManager can use the path to open the correct hierarchy
  12.    * of pages and overlay(s). Handlers for user events, like pressing buttons,
  13.    * can call into PageManager to open a particular overlay or cancel an
  14.    * existing overlay.
  15.    */
  16.   var PageManager = {
  17.     /**
  18.      * True if page is served from a dialog.
  19.      * @type {boolean}
  20.      */
  21.     isDialog: false,
  22.  
  23.     /**
  24.      * Offset of page container in pixels. Uber pages that use the side menu
  25.      * can override this with the setter.
  26.      * The default (23) comes from -webkit-margin-start in uber_shared.css.
  27.      * @type {number}
  28.      */
  29.     horizontalOffset_: 23,
  30.  
  31.     /**
  32.      * Root pages. Maps lower-case page names to the respective page object.
  33.      * @type {!Object.<string, !cr.ui.pageManager.Page>}
  34.      */
  35.     registeredPages: {},
  36.  
  37.     /**
  38.      * Pages which are meant to behave like modal dialogs. Maps lower-case
  39.      * overlay names to the respective overlay object.
  40.      * @type {!Object.<string, !cr.ui.pageManager.Page>}
  41.      * @private
  42.      */
  43.     registeredOverlayPages: {},
  44.  
  45.     /**
  46.      * Observers will be notified when opening and closing overlays.
  47.      * @type {!Array.<!cr.ui.pageManager.PageManager.Observer>}
  48.      */
  49.     observers_: [],
  50.  
  51.     /**
  52.      * Initializes the complete page.
  53.      * @param {cr.ui.pageManager.Page} defaultPage The page to be shown when no
  54.      *     page is specified in the path.
  55.      */
  56.     initialize: function(defaultPage) {
  57.       this.defaultPage_ = defaultPage;
  58.  
  59.       FocusOutlineManager.forDocument(document);
  60.       document.addEventListener('scroll', this.handleScroll_.bind(this));
  61.  
  62.       // Trigger the scroll handler manually to set the initial state.
  63.       this.handleScroll_();
  64.  
  65.       // Shake the dialog if the user clicks outside the dialog bounds.
  66.       var containers = document.querySelectorAll('body > .overlay');
  67.       for (var i = 0; i < containers.length; i++) {
  68.         var overlay = containers[i];
  69.         cr.ui.overlay.setupOverlay(overlay);
  70.         overlay.addEventListener('cancelOverlay',
  71.                                  this.cancelOverlay.bind(this));
  72.       }
  73.  
  74.       cr.ui.overlay.globalInitialization();
  75.     },
  76.  
  77.     /**
  78.      * Registers new page.
  79.      * @param {!cr.ui.pageManager.Page} page Page to register.
  80.      */
  81.     register: function(page) {
  82.       this.registeredPages[page.name.toLowerCase()] = page;
  83.       page.initializePage();
  84.     },
  85.  
  86.     /**
  87.      * Registers a new Overlay page.
  88.      * @param {!cr.ui.pageManager.Page} overlay Overlay to register.
  89.      * @param {cr.ui.pageManager.Page} parentPage Associated parent page for
  90.      *     this overlay.
  91.      * @param {Array} associatedControls Array of control elements associated
  92.      *     with this page.
  93.      */
  94.     registerOverlay: function(overlay,
  95.                               parentPage,
  96.                               associatedControls) {
  97.       this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
  98.       overlay.parentPage = parentPage;
  99.       if (associatedControls) {
  100.         overlay.associatedControls = associatedControls;
  101.         if (associatedControls.length) {
  102.           overlay.associatedSection =
  103.               this.findSectionForNode_(associatedControls[0]);
  104.         }
  105.  
  106.         // Sanity check.
  107.         for (var i = 0; i < associatedControls.length; ++i) {
  108.           assert(associatedControls[i], 'Invalid element passed.');
  109.         }
  110.       }
  111.  
  112.       overlay.tab = undefined;
  113.       overlay.isOverlay = true;
  114.  
  115.       overlay.reverseButtonStrip();
  116.       overlay.initializePage();
  117.     },
  118.  
  119.     /**
  120.      * Shows the default page.
  121.      * @param {boolean=} opt_updateHistory If we should update the history after
  122.      *     showing the page (defaults to true).
  123.      */
  124.     showDefaultPage: function(opt_updateHistory) {
  125.       assert(this.defaultPage_ instanceof cr.ui.pageManager.Page,
  126.              'PageManager must be initialized with a default page.');
  127.       this.showPageByName(this.defaultPage_.name, opt_updateHistory);
  128.     },
  129.  
  130.     /**
  131.      * Shows a registered page. This handles both root and overlay pages.
  132.      * @param {string} pageName Page name.
  133.      * @param {boolean=} opt_updateHistory If we should update the history after
  134.      *     showing the page (defaults to true).
  135.      * @param {Object=} opt_propertyBag An optional bag of properties including
  136.      *     replaceState (if history state should be replaced instead of pushed).
  137.      *     hash (a hash state to attach to the page).
  138.      */
  139.     showPageByName: function(pageName,
  140.                              opt_updateHistory,
  141.                              opt_propertyBag) {
  142.       opt_updateHistory = opt_updateHistory !== false;
  143.       opt_propertyBag = opt_propertyBag || {};
  144.  
  145.       // If a bubble is currently being shown, hide it.
  146.       this.hideBubble();
  147.  
  148.       // Find the currently visible root-level page.
  149.       var rootPage = null;
  150.       for (var name in this.registeredPages) {
  151.         var page = this.registeredPages[name];
  152.         if (page.visible && !page.parentPage) {
  153.           rootPage = page;
  154.           break;
  155.         }
  156.       }
  157.  
  158.       // Find the target page.
  159.       var targetPage = this.registeredPages[pageName.toLowerCase()];
  160.       if (!targetPage || !targetPage.canShowPage()) {
  161.         // If it's not a page, try it as an overlay.
  162.         var hash = opt_propertyBag.hash || '';
  163.         if (!targetPage && this.showOverlay_(pageName, hash, rootPage)) {
  164.           if (opt_updateHistory)
  165.             this.updateHistoryState_(!!opt_propertyBag.replaceState);
  166.           this.updateTitle_();
  167.           return;
  168.         }
  169.         targetPage = this.defaultPage_;
  170.       }
  171.  
  172.       pageName = targetPage.name.toLowerCase();
  173.       var targetPageWasVisible = targetPage.visible;
  174.  
  175.       // Determine if the root page is 'sticky', meaning that it
  176.       // shouldn't change when showing an overlay. This can happen for special
  177.       // pages like Search.
  178.       var isRootPageLocked =
  179.           rootPage && rootPage.sticky && targetPage.parentPage;
  180.  
  181.       // Notify pages if they will be hidden.
  182.       this.forEachPage_(!isRootPageLocked, function(page) {
  183.         if (page.name != pageName && !this.isAncestorOfPage(page, targetPage))
  184.           page.willHidePage();
  185.       });
  186.  
  187.       // Update the page's hash.
  188.       targetPage.hash = opt_propertyBag.hash || '';
  189.  
  190.       // Update visibilities to show only the hierarchy of the target page.
  191.       this.forEachPage_(!isRootPageLocked, function(page) {
  192.         page.visible = page.name == pageName ||
  193.                        this.isAncestorOfPage(page, targetPage);
  194.       });
  195.  
  196.       // Update the history and current location.
  197.       if (opt_updateHistory)
  198.         this.updateHistoryState_(!!opt_propertyBag.replaceState);
  199.  
  200.       // Update focus if any other control was focused on the previous page,
  201.       // or the previous page is not known.
  202.       if (document.activeElement != document.body &&
  203.           (!rootPage || rootPage.pageDiv.contains(document.activeElement))) {
  204.         targetPage.focus();
  205.       }
  206.  
  207.       // Notify pages if they were shown.
  208.       this.forEachPage_(!isRootPageLocked, function(page) {
  209.         if (!targetPageWasVisible &&
  210.             (page.name == pageName ||
  211.              this.isAncestorOfPage(page, targetPage))) {
  212.           page.didShowPage();
  213.         }
  214.       });
  215.  
  216.       // If the target page was already visible, notify it that its hash
  217.       // changed externally.
  218.       if (targetPageWasVisible)
  219.         targetPage.didChangeHash();
  220.  
  221.       // Update the document title. Do this after didShowPage was called, in
  222.       // case a page decides to change its title.
  223.       this.updateTitle_();
  224.     },
  225.  
  226.     /**
  227.      * Returns the name of the page from the current path.
  228.      * @return {string} Name of the page specified by the current path.
  229.      */
  230.     getPageNameFromPath: function() {
  231.       var path = location.pathname;
  232.       if (path.length <= 1)
  233.         return this.defaultPage_.name;
  234.  
  235.       // Skip starting slash and remove trailing slash (if any).
  236.       return path.slice(1).replace(/\/$/, '');
  237.     },
  238.  
  239.     /**
  240.      * Gets the level of the page. Root pages (e.g., BrowserOptions) are at
  241.      * level 0.
  242.      * @return {number} How far down this page is from the root page.
  243.      */
  244.     getNestingLevel: function(page) {
  245.       var level = 0;
  246.       var parent = page.parentPage;
  247.       while (parent) {
  248.         level++;
  249.         parent = parent.parentPage;
  250.       }
  251.       return level;
  252.     },
  253.  
  254.     /**
  255.      * Checks whether one page is an ancestor of the other page in terms of
  256.      * subpage nesting.
  257.      * @param {cr.ui.pageManager.Page} potentialAncestor Potential ancestor.
  258.      * @param {cr.ui.pageManager.Page} potentialDescendent Potential descendent.
  259.      * @return {boolean} True if |potentialDescendent| is nested under
  260.      *     |potentialAncestor|.
  261.      */
  262.     isAncestorOfPage: function(potentialAncestor, potentialDescendent) {
  263.       var parent = potentialDescendent.parentPage;
  264.       while (parent) {
  265.         if (parent == potentialAncestor)
  266.           return true;
  267.         parent = parent.parentPage;
  268.       }
  269.       return false;
  270.     },
  271.  
  272.     /**
  273.      * Returns true if the page is a direct descendent of a root page, or if
  274.      * the page is considered always on top. Doesn't consider visibility.
  275.      * @param {cr.ui.pageManager.Page} page Page to check.
  276.      * @return {boolean} True if |page| is a top-level overlay.
  277.      */
  278.     isTopLevelOverlay: function(page) {
  279.       return page.isOverlay &&
  280.             (page.alwaysOnTop || this.getNestingLevel(page) == 1);
  281.     },
  282.  
  283.     /**
  284.      * Called when an page is shown or hidden to update the root page
  285.      * based on the page's new visibility.
  286.      * @param {cr.ui.pageManager.Page} page The page being made visible or
  287.      *     invisible.
  288.      */
  289.     onPageVisibilityChanged: function(page) {
  290.       this.updateRootPageFreezeState();
  291.  
  292.       for (var i = 0; i < this.observers_.length; ++i)
  293.         this.observers_[i].onPageVisibilityChanged(page);
  294.  
  295.       if (!page.visible && this.isTopLevelOverlay(page))
  296.         this.updateScrollPosition_();
  297.     },
  298.  
  299.     /**
  300.      * Called when a page's hash changes. If the page is the topmost visible
  301.      * page, the history state is updated.
  302.      * @param {cr.ui.pageManager.Page} page The page whose hash has changed.
  303.      */
  304.     onPageHashChanged: function(page) {
  305.       if (page == this.getTopmostVisiblePage())
  306.         this.updateHistoryState_(false);
  307.     },
  308.  
  309.     /**
  310.      * Returns the topmost visible page, or null if no page is visible.
  311.      * @return {cr.ui.pageManager.Page} The topmost visible page.
  312.      */
  313.     getTopmostVisiblePage: function() {
  314.       // Check overlays first since they're top-most if visible.
  315.       return this.getVisibleOverlay_() ||
  316.           this.getTopmostVisibleNonOverlayPage_();
  317.     },
  318.  
  319.     /**
  320.      * Closes the visible overlay. Updates the history state after closing the
  321.      * overlay.
  322.      */
  323.     closeOverlay: function() {
  324.       var overlay = this.getVisibleOverlay_();
  325.       if (!overlay)
  326.         return;
  327.  
  328.       overlay.visible = false;
  329.       overlay.didClosePage();
  330.  
  331.       this.updateHistoryState_(false);
  332.       this.updateTitle_();
  333.  
  334.       this.restoreLastFocusedElement_();
  335.     },
  336.  
  337.     /**
  338.      * Closes all overlays and updates the history after each closed overlay.
  339.      */
  340.     closeAllOverlays: function() {
  341.       while (this.isOverlayVisible_()) {
  342.         this.closeOverlay();
  343.       }
  344.     },
  345.  
  346.     /**
  347.      * Cancels (closes) the overlay, due to the user pressing <Esc>.
  348.      */
  349.     cancelOverlay: function() {
  350.       // Blur the active element to ensure any changed pref value is saved.
  351.       document.activeElement.blur();
  352.       var overlay = this.getVisibleOverlay_();
  353.       if (!overlay)
  354.         return;
  355.       // Let the overlay handle the <Esc> if it wants to.
  356.       if (overlay.handleCancel) {
  357.         overlay.handleCancel();
  358.         this.restoreLastFocusedElement_();
  359.       } else {
  360.         this.closeOverlay();
  361.       }
  362.     },
  363.  
  364.     /**
  365.      * Shows an informational bubble displaying |content| and pointing at the
  366.      * |target| element. If |content| has focusable elements, they join the
  367.      * current page's tab order as siblings of |domSibling|.
  368.      * @param {HTMLDivElement} content The content of the bubble.
  369.      * @param {HTMLElement} target The element at which the bubble points.
  370.      * @param {HTMLElement} domSibling The element after which the bubble is
  371.      *     added to the DOM.
  372.      * @param {cr.ui.ArrowLocation} location The arrow location.
  373.      */
  374.     showBubble: function(content, target, domSibling, location) {
  375.       this.hideBubble();
  376.  
  377.       var bubble = new cr.ui.AutoCloseBubble;
  378.       bubble.anchorNode = target;
  379.       bubble.domSibling = domSibling;
  380.       bubble.arrowLocation = location;
  381.       bubble.content = content;
  382.       bubble.show();
  383.       this.bubble_ = bubble;
  384.     },
  385.  
  386.     /**
  387.      * Hides the currently visible bubble, if any.
  388.      */
  389.     hideBubble: function() {
  390.       if (this.bubble_)
  391.         this.bubble_.hide();
  392.     },
  393.  
  394.     /**
  395.      * Returns the currently visible bubble, or null if no bubble is visible.
  396.      * @return {cr.ui.AutoCloseBubble} The bubble currently being shown.
  397.      */
  398.     getVisibleBubble: function() {
  399.       var bubble = this.bubble_;
  400.       return bubble && !bubble.hidden ? bubble : null;
  401.     },
  402.  
  403.     /**
  404.      * Callback for window.onpopstate to handle back/forward navigations.
  405.      * @param {string} pageName The current page name.
  406.      * @param {string} hash The hash to pass into the page.
  407.      * @param {Object} data State data pushed into history.
  408.      */
  409.     setState: function(pageName, hash, data) {
  410.       var currentOverlay = this.getVisibleOverlay_();
  411.       var lowercaseName = pageName.toLowerCase();
  412.       var newPage = this.registeredPages[lowercaseName] ||
  413.                     this.registeredOverlayPages[lowercaseName] ||
  414.                     this.defaultPage_;
  415.       if (currentOverlay && !this.isAncestorOfPage(currentOverlay, newPage)) {
  416.         currentOverlay.visible = false;
  417.         currentOverlay.didClosePage();
  418.       }
  419.       this.showPageByName(pageName, false, {hash: hash});
  420.     },
  421.  
  422.  
  423.     /**
  424.      * Whether the page is still loading (i.e. onload hasn't finished running).
  425.      * @return {boolean} Whether the page is still loading.
  426.      */
  427.     isLoading: function() {
  428.       return document.documentElement.classList.contains('loading');
  429.     },
  430.  
  431.     /**
  432.      * Callback for window.onbeforeunload. Used to notify overlays that they
  433.      * will be closed.
  434.      */
  435.     willClose: function() {
  436.       var overlay = this.getVisibleOverlay_();
  437.       if (overlay)
  438.         overlay.didClosePage();
  439.     },
  440.  
  441.     /**
  442.      * Freezes/unfreezes the scroll position of the root page based on the
  443.      * current page stack.
  444.      */
  445.     updateRootPageFreezeState: function() {
  446.       var topPage = this.getTopmostVisiblePage();
  447.       if (topPage)
  448.         this.setRootPageFrozen_(topPage.isOverlay);
  449.     },
  450.  
  451.     /**
  452.      * Change the horizontal offset used to reposition elements while showing an
  453.      * overlay from the default.
  454.      */
  455.     set horizontalOffset(value) {
  456.       this.horizontalOffset_ = value;
  457.     },
  458.  
  459.     /**
  460.      * @param {!cr.ui.pageManager.PageManager.Observer} observer The observer to
  461.      *     register.
  462.      */
  463.     addObserver: function(observer) {
  464.       this.observers_.push(observer);
  465.     },
  466.  
  467.     /**
  468.      * Shows a registered overlay page. Does not update history.
  469.      * @param {string} overlayName Page name.
  470.      * @param {string} hash The hash state to associate with the overlay.
  471.      * @param {cr.ui.pageManager.Page} rootPage The currently visible root-level
  472.      *     page.
  473.      * @return {boolean} Whether we showed an overlay.
  474.      * @private
  475.      */
  476.     showOverlay_: function(overlayName, hash, rootPage) {
  477.       var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
  478.       if (!overlay || !overlay.canShowPage())
  479.         return false;
  480.  
  481.       // Save the currently focused element in the page for restoration later.
  482.       var currentPage = this.getTopmostVisiblePage();
  483.       if (currentPage)
  484.         currentPage.lastFocusedElement = document.activeElement;
  485.  
  486.       if ((!rootPage || !rootPage.sticky) &&
  487.           overlay.parentPage &&
  488.           !overlay.parentPage.visible) {
  489.         this.showPageByName(overlay.parentPage.name, false);
  490.       }
  491.  
  492.       overlay.hash = hash;
  493.       if (!overlay.visible) {
  494.         overlay.visible = true;
  495.         overlay.didShowPage();
  496.       } else {
  497.         overlay.didChangeHash();
  498.       }
  499.  
  500.       // Change focus to the overlay if any other control was focused by
  501.       // keyboard before. Otherwise, no one should have focus.
  502.       if (document.activeElement != document.body) {
  503.         if (FocusOutlineManager.forDocument(document).visible) {
  504.           overlay.focus();
  505.         } else if (!overlay.pageDiv.contains(document.activeElement)) {
  506.           document.activeElement.blur();
  507.         }
  508.       }
  509.  
  510.       if ($('search-field') && $('search-field').value == '') {
  511.         var section = overlay.associatedSection;
  512.         if (section)
  513.           options.BrowserOptions.scrollToSection(section);
  514.       }
  515.  
  516.       return true;
  517.     },
  518.  
  519.     /**
  520.      * Returns whether or not an overlay is visible.
  521.      * @return {boolean} True if an overlay is visible.
  522.      * @private
  523.      */
  524.     isOverlayVisible_: function() {
  525.       return this.getVisibleOverlay_() != null;
  526.     },
  527.  
  528.     /**
  529.      * Returns the currently visible overlay, or null if no page is visible.
  530.      * @return {cr.ui.pageManager.Page} The visible overlay.
  531.      * @private
  532.      */
  533.     getVisibleOverlay_: function() {
  534.       var topmostPage = null;
  535.       for (var name in this.registeredOverlayPages) {
  536.         var page = this.registeredOverlayPages[name];
  537.         if (!page.visible)
  538.           continue;
  539.  
  540.         if (page.alwaysOnTop)
  541.           return page;
  542.  
  543.         if (!topmostPage ||
  544.              this.getNestingLevel(page) > this.getNestingLevel(topmostPage)) {
  545.           topmostPage = page;
  546.         }
  547.       }
  548.       return topmostPage;
  549.     },
  550.  
  551.     /**
  552.      * Returns the topmost visible page (overlays excluded).
  553.      * @return {cr.ui.pageManager.Page} The topmost visible page aside from any
  554.      *     overlays.
  555.      * @private
  556.      */
  557.     getTopmostVisibleNonOverlayPage_: function() {
  558.       for (var name in this.registeredPages) {
  559.         var page = this.registeredPages[name];
  560.         if (page.visible)
  561.           return page;
  562.       }
  563.  
  564.       return null;
  565.     },
  566.  
  567.     /**
  568.      * Scrolls the page to the correct position (the top when opening an
  569.      * overlay, or the old scroll position a previously hidden overlay
  570.      * becomes visible).
  571.      * @private
  572.      */
  573.     updateScrollPosition_: function() {
  574.       var container = $('page-container');
  575.       var scrollTop = container.oldScrollTop || 0;
  576.       container.oldScrollTop = undefined;
  577.       window.scroll(scrollLeftForDocument(document), scrollTop);
  578.     },
  579.  
  580.     /**
  581.      * Updates the title to the title of the current page, or of the topmost
  582.      * visible page with a non-empty title.
  583.      * @private
  584.      */
  585.     updateTitle_: function() {
  586.       var page = this.getTopmostVisiblePage();
  587.       while (page) {
  588.         if (page.title) {
  589.           for (var i = 0; i < this.observers_.length; ++i) {
  590.             this.observers_[i].updateTitle(page.title);
  591.           }
  592.           return;
  593.         }
  594.         page = page.parentPage;
  595.       }
  596.     },
  597.  
  598.     /**
  599.      * Constructs a new path to push onto the history stack, using observers
  600.      * to update the history.
  601.      * @param {boolean} replace If true, handlers should replace the current
  602.      *     history event rather than create new ones.
  603.      * @private
  604.      */
  605.     updateHistoryState_: function(replace) {
  606.       if (this.isDialog)
  607.         return;
  608.  
  609.       var page = this.getTopmostVisiblePage();
  610.       var path = window.location.pathname + window.location.hash;
  611.       if (path) {
  612.         // Remove trailing slash.
  613.         path = path.slice(1).replace(/\/(?:#|$)/, '');
  614.       }
  615.  
  616.       // If the page is already in history (the user may have clicked the same
  617.       // link twice, or this is the initial load), do nothing.
  618.       var newPath = (page == this.defaultPage_ ? '' : page.name) + page.hash;
  619.       if (path == newPath)
  620.         return;
  621.  
  622.       for (var i = 0; i < this.observers_.length; ++i) {
  623.         this.observers_[i].updateHistory(newPath, replace);
  624.       }
  625.     },
  626.  
  627.     /**
  628.      * Restores the last focused element on a given page.
  629.      * @private
  630.      */
  631.     restoreLastFocusedElement_: function() {
  632.       var currentPage = this.getTopmostVisiblePage();
  633.       if (currentPage.lastFocusedElement)
  634.         currentPage.lastFocusedElement.focus();
  635.     },
  636.  
  637.     /**
  638.      * Find an enclosing section for an element if it exists.
  639.      * @param {Node} node Element to search.
  640.      * @return {Node} The section element, or null.
  641.      * @private
  642.      */
  643.     findSectionForNode_: function(node) {
  644.       while (node = node.parentNode) {
  645.         if (node.nodeName == 'SECTION')
  646.           return node;
  647.       }
  648.       return null;
  649.     },
  650.  
  651.     /**
  652.      * Freezes/unfreezes the scroll position of the root page container.
  653.      * @param {boolean} freeze Whether the page should be frozen.
  654.      * @private
  655.      */
  656.     setRootPageFrozen_: function(freeze) {
  657.       var container = $('page-container');
  658.       if (container.classList.contains('frozen') == freeze)
  659.         return;
  660.  
  661.       if (freeze) {
  662.         // Lock the width, since auto width computation may change.
  663.         container.style.width = window.getComputedStyle(container).width;
  664.         container.oldScrollTop = scrollTopForDocument(document);
  665.         container.classList.add('frozen');
  666.         var verticalPosition =
  667.             container.getBoundingClientRect().top - container.oldScrollTop;
  668.         container.style.top = verticalPosition + 'px';
  669.         this.updateFrozenElementHorizontalPosition_(container);
  670.       } else {
  671.         container.classList.remove('frozen');
  672.         container.style.top = '';
  673.         container.style.left = '';
  674.         container.style.right = '';
  675.         container.style.width = '';
  676.       }
  677.     },
  678.  
  679.     /**
  680.      * Called when the page is scrolled; moves elements that are position:fixed
  681.      * but should only behave as if they are fixed for vertical scrolling.
  682.      * @private
  683.      */
  684.     handleScroll_: function() {
  685.       this.updateAllFrozenElementPositions_();
  686.     },
  687.  
  688.     /**
  689.      * Updates all frozen pages to match the horizontal scroll position.
  690.      * @private
  691.      */
  692.     updateAllFrozenElementPositions_: function() {
  693.       var frozenElements = document.querySelectorAll('.frozen');
  694.       for (var i = 0; i < frozenElements.length; i++)
  695.         this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
  696.     },
  697.  
  698.     /**
  699.      * Updates the given frozen element to match the horizontal scroll position.
  700.      * @param {HTMLElement} e The frozen element to update.
  701.      * @private
  702.      */
  703.     updateFrozenElementHorizontalPosition_: function(e) {
  704.       if (isRTL()) {
  705.         e.style.right = this.horizontalOffset + 'px';
  706.       } else {
  707.         var scrollLeft = scrollLeftForDocument(document);
  708.         e.style.left = this.horizontalOffset - scrollLeft + 'px';
  709.       }
  710.     },
  711.  
  712.     /**
  713.      * Calls the given callback with each registered page.
  714.      * @param {boolean} includeRootPages Whether the callback should be called
  715.      *     for the root pages.
  716.      * @param {function(cr.ui.pageManager.Page)} callback The callback.
  717.      * @private
  718.      */
  719.     forEachPage_: function(includeRootPages, callback) {
  720.       var pageNames = Object.keys(this.registeredOverlayPages);
  721.       if (includeRootPages)
  722.         pageNames = Object.keys(this.registeredPages).concat(pageNames);
  723.  
  724.       pageNames.forEach(function(name) {
  725.         callback.call(this, this.registeredOverlayPages[name] ||
  726.                             this.registeredPages[name]);
  727.       }, this);
  728.     },
  729.   };
  730.  
  731.   /**
  732.    * An observer of PageManager.
  733.    * @interface
  734.    */
  735.   PageManager.Observer = function() {}
  736.  
  737.   PageManager.Observer.prototype = {
  738.     /**
  739.      * Called when a page is being shown or has been hidden.
  740.      * @param {cr.ui.pageManager.Page} page The page being shown or hidden.
  741.      */
  742.     onPageVisibilityChanged: function(page) {},
  743.  
  744.     /**
  745.      * Called when a new title should be set.
  746.      * @param {string} title The title to set.
  747.      */
  748.     updateTitle: function(title) {},
  749.  
  750.     /**
  751.      * Called when a page is navigated to.
  752.      * @param {string} path The path of the page being visited.
  753.      * @param {boolean} replace If true, allow no history events to be created.
  754.      */
  755.     updateHistory: function(path, replace) {},
  756.   };
  757.  
  758.   // Export
  759.   return {
  760.     PageManager: PageManager
  761.   };
  762. });
  763.